Honor css defined paragraph intent
This commit is contained in:
@@ -291,14 +291,12 @@ std::vector<size_t> ParsedText::computeLineBreaks(const GfxRenderer& renderer, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate first line indent (only for left/justified text).
|
// Calculate first line indent (only for left/justified text).
|
||||||
// Positive text-indent (paragraph indent) is suppressed when extraParagraphSpacing is on.
|
// Explicit CSS text-indent always applies — author intent overrides the extraParagraphSpacing
|
||||||
// Negative text-indent (hanging indent, e.g. margin-left:3em; text-indent:-1em) always applies —
|
// toggle. Only the implicit EmSpace fallback in applyParagraphIndent() is gated on it.
|
||||||
// it is structural (positions the bullet/marker), not decorative.
|
const int firstLineIndent = blockStyle.textIndentDefined && (blockStyle.alignment == CssTextAlign::Justify ||
|
||||||
const int firstLineIndent =
|
blockStyle.alignment == CssTextAlign::Left)
|
||||||
blockStyle.textIndentDefined && (blockStyle.textIndent < 0 || !extraParagraphSpacing) &&
|
? blockStyle.textIndent
|
||||||
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
: 0;
|
||||||
? blockStyle.textIndent
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// Ensure any word that would overflow even as the first entry on a line is split using fallback hyphenation.
|
// Ensure any word that would overflow even as the first entry on a line is split using fallback hyphenation.
|
||||||
for (size_t i = 0; i < wordWidths.size(); ++i) {
|
for (size_t i = 0; i < wordWidths.size(); ++i) {
|
||||||
@@ -428,7 +426,7 @@ size_t ParsedText::computeSingleLineBreakNoHyphen(const GfxRenderer& renderer, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int firstLineIndent =
|
const int firstLineIndent =
|
||||||
lineStartIndex == 0 && blockStyle.textIndentDefined && (blockStyle.textIndent < 0 || !extraParagraphSpacing) &&
|
lineStartIndex == 0 && blockStyle.textIndentDefined &&
|
||||||
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
||||||
? blockStyle.textIndent
|
? blockStyle.textIndent
|
||||||
: 0;
|
: 0;
|
||||||
@@ -471,15 +469,17 @@ size_t ParsedText::computeSingleLineBreakNoHyphen(const GfxRenderer& renderer, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ParsedText::applyParagraphIndent() {
|
void ParsedText::applyParagraphIndent() {
|
||||||
if (extraParagraphSpacing || words.empty()) {
|
if (words.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockStyle.textIndentDefined) {
|
if (blockStyle.textIndentDefined) {
|
||||||
// CSS text-indent is explicitly set (even if 0) - don't use fallback EmSpace
|
// CSS text-indent is explicitly set (even if 0) - don't use fallback EmSpace.
|
||||||
// The actual indent positioning is handled in extractLine()
|
// The actual indent positioning is handled in extractLine().
|
||||||
} else if (blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left) {
|
} else if (!extraParagraphSpacing &&
|
||||||
// No CSS text-indent defined - use EmSpace fallback for visual indent
|
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)) {
|
||||||
|
// No CSS text-indent defined - use EmSpace fallback only when extra paragraph spacing is off,
|
||||||
|
// so paragraphs remain visually distinguishable.
|
||||||
words.front().insert(0, "\xe2\x80\x83");
|
words.front().insert(0, "\xe2\x80\x83");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -492,14 +492,12 @@ std::vector<size_t> ParsedText::computeHyphenatedLineBreaks(const GfxRenderer& r
|
|||||||
std::vector<int>& splitPrefixWordIndexes,
|
std::vector<int>& splitPrefixWordIndexes,
|
||||||
std::vector<bool>& splitInsertedHyphen) {
|
std::vector<bool>& splitInsertedHyphen) {
|
||||||
// Calculate first line indent (only for left/justified text).
|
// Calculate first line indent (only for left/justified text).
|
||||||
// Positive text-indent (paragraph indent) is suppressed when extraParagraphSpacing is on.
|
// Explicit CSS text-indent always applies — author intent overrides the extraParagraphSpacing
|
||||||
// Negative text-indent (hanging indent, e.g. margin-left:3em; text-indent:-1em) always applies —
|
// toggle. Only the implicit EmSpace fallback in applyParagraphIndent() is gated on it.
|
||||||
// it is structural (positions the bullet/marker), not decorative.
|
const int firstLineIndent = blockStyle.textIndentDefined && (blockStyle.alignment == CssTextAlign::Justify ||
|
||||||
const int firstLineIndent =
|
blockStyle.alignment == CssTextAlign::Left)
|
||||||
blockStyle.textIndentDefined && (blockStyle.textIndent < 0 || !extraParagraphSpacing) &&
|
? blockStyle.textIndent
|
||||||
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
: 0;
|
||||||
? blockStyle.textIndent
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// Pre-compute inter-word gaps to avoid repeated codepoint scanning and renderer
|
// Pre-compute inter-word gaps to avoid repeated codepoint scanning and renderer
|
||||||
// calls in the inner loop. When hyphenateWordAtIndex inserts a new word, we insert
|
// calls in the inner loop. When hyphenateWordAtIndex inserts a new word, we insert
|
||||||
@@ -785,12 +783,11 @@ ParsedText::LineProcessResult ParsedText::extractLine(
|
|||||||
const size_t lineWordCount = lineBreak - lastBreakAt;
|
const size_t lineWordCount = lineBreak - lastBreakAt;
|
||||||
|
|
||||||
// Calculate first line indent (only for left/justified text).
|
// Calculate first line indent (only for left/justified text).
|
||||||
// Positive text-indent (paragraph indent) is suppressed when extraParagraphSpacing is on.
|
// Explicit CSS text-indent always applies — author intent overrides the extraParagraphSpacing
|
||||||
// Negative text-indent (hanging indent, e.g. margin-left:3em; text-indent:-1em) always applies —
|
// toggle. Only the implicit EmSpace fallback in applyParagraphIndent() is gated on it.
|
||||||
// it is structural (positions the bullet/marker), not decorative.
|
|
||||||
const bool isFirstLine = breakIndex == 0;
|
const bool isFirstLine = breakIndex == 0;
|
||||||
const int firstLineIndent =
|
const int firstLineIndent =
|
||||||
isFirstLine && blockStyle.textIndentDefined && (blockStyle.textIndent < 0 || !extraParagraphSpacing) &&
|
isFirstLine && blockStyle.textIndentDefined &&
|
||||||
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
||||||
? blockStyle.textIndent
|
? blockStyle.textIndent
|
||||||
: 0;
|
: 0;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#include "parsers/ChapterHtmlSlimParser.h"
|
#include "parsers/ChapterHtmlSlimParser.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t SECTION_FILE_VERSION = 22;
|
constexpr uint8_t SECTION_FILE_VERSION = 23;
|
||||||
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + // SECTION_FILE_VERSION
|
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + // SECTION_FILE_VERSION
|
||||||
sizeof(int) + // fontId
|
sizeof(int) + // fontId
|
||||||
sizeof(float) + // lineCompression
|
sizeof(float) + // lineCompression
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "Epub/css/CssStyle.h"
|
#include "Epub/css/CssStyle.h"
|
||||||
@@ -8,6 +9,12 @@
|
|||||||
* BlockStyle - Block-level styling properties
|
* BlockStyle - Block-level styling properties
|
||||||
*/
|
*/
|
||||||
struct BlockStyle {
|
struct BlockStyle {
|
||||||
|
// Upper bound (in em) for any single side's horizontal margin or padding.
|
||||||
|
// Some EPUBs apply huge em-based insets to chapter-opener classes; without a
|
||||||
|
// cap, effectiveWidth collapses to 1-2 words per line and justification dumps
|
||||||
|
// the remaining space into a single gap.
|
||||||
|
static constexpr float MAX_HORIZONTAL_INSET_EM = 4.0f;
|
||||||
|
|
||||||
CssTextAlign alignment = CssTextAlign::Justify;
|
CssTextAlign alignment = CssTextAlign::Justify;
|
||||||
|
|
||||||
// Spacing (in pixels)
|
// Spacing (in pixels)
|
||||||
@@ -75,16 +82,17 @@ struct BlockStyle {
|
|||||||
const uint16_t viewportWidth = 0) {
|
const uint16_t viewportWidth = 0) {
|
||||||
BlockStyle blockStyle;
|
BlockStyle blockStyle;
|
||||||
const float vw = viewportWidth;
|
const float vw = viewportWidth;
|
||||||
|
const auto maxHorizontalInsetPx = static_cast<int16_t>(emSize * MAX_HORIZONTAL_INSET_EM);
|
||||||
// Resolve all CssLength values to pixels using the current font's em size and viewport width
|
// Resolve all CssLength values to pixels using the current font's em size and viewport width
|
||||||
blockStyle.marginTop = cssStyle.marginTop.toPixelsInt16(emSize, vw);
|
blockStyle.marginTop = cssStyle.marginTop.toPixelsInt16(emSize, vw);
|
||||||
blockStyle.marginBottom = cssStyle.marginBottom.toPixelsInt16(emSize, vw);
|
blockStyle.marginBottom = cssStyle.marginBottom.toPixelsInt16(emSize, vw);
|
||||||
blockStyle.marginLeft = cssStyle.marginLeft.toPixelsInt16(emSize, vw);
|
blockStyle.marginLeft = std::min(cssStyle.marginLeft.toPixelsInt16(emSize, vw), maxHorizontalInsetPx);
|
||||||
blockStyle.marginRight = cssStyle.marginRight.toPixelsInt16(emSize, vw);
|
blockStyle.marginRight = std::min(cssStyle.marginRight.toPixelsInt16(emSize, vw), maxHorizontalInsetPx);
|
||||||
|
|
||||||
blockStyle.paddingTop = cssStyle.paddingTop.toPixelsInt16(emSize, vw);
|
blockStyle.paddingTop = cssStyle.paddingTop.toPixelsInt16(emSize, vw);
|
||||||
blockStyle.paddingBottom = cssStyle.paddingBottom.toPixelsInt16(emSize, vw);
|
blockStyle.paddingBottom = cssStyle.paddingBottom.toPixelsInt16(emSize, vw);
|
||||||
blockStyle.paddingLeft = cssStyle.paddingLeft.toPixelsInt16(emSize, vw);
|
blockStyle.paddingLeft = std::min(cssStyle.paddingLeft.toPixelsInt16(emSize, vw), maxHorizontalInsetPx);
|
||||||
blockStyle.paddingRight = cssStyle.paddingRight.toPixelsInt16(emSize, vw);
|
blockStyle.paddingRight = std::min(cssStyle.paddingRight.toPixelsInt16(emSize, vw), maxHorizontalInsetPx);
|
||||||
|
|
||||||
// For textIndent: if it's a percentage we can't resolve (no viewport width),
|
// For textIndent: if it's a percentage we can't resolve (no viewport width),
|
||||||
// leave textIndentDefined=false so the EmSpace fallback in applyParagraphIndent() is used
|
// leave textIndentDefined=false so the EmSpace fallback in applyParagraphIndent() is used
|
||||||
|
|||||||
Reference in New Issue
Block a user