From e242e8f8a40c2c1496e6f23fedd323104102efb2 Mon Sep 17 00:00:00 2001 From: zmiao Date: Tue, 24 Sep 2019 18:53:36 +0300 Subject: [PATCH] Update font baseline, make vertical mode applying with font baseline --- src/symbol/quads.js | 12 +++++---- src/symbol/shaping.js | 23 +++++++++--------- .../expected.png | Bin 0 -> 9109 bytes .../style.json | 1 + .../text-font/mixed-fonts/expected.png | Bin 9015 -> 0 bytes 5 files changed, 20 insertions(+), 16 deletions(-) create mode 100644 test/integration/render-tests/text-font/mixed-fonts-both-with-baseline/expected.png rename test/integration/render-tests/text-font/{mixed-fonts => mixed-fonts-both-with-baseline}/style.json (97%) delete mode 100644 test/integration/render-tests/text-font/mixed-fonts/expected.png diff --git a/src/symbol/quads.js b/src/symbol/quads.js index 6d3b857f717..01c1347cfea 100644 --- a/src/symbol/quads.js +++ b/src/symbol/quads.js @@ -116,7 +116,7 @@ export function getGlyphQuads(anchor: Anchor, const rect = glyph.rect; if (!rect) continue; - // The rects have an addditional buffer that is not included in their size. + // The rects have an additional buffer that is not included in their size. const glyphPadding = 1.0; const rectBuffer = GLYPH_PBF_BORDER + glyphPadding; @@ -152,21 +152,23 @@ export function getGlyphQuads(anchor: Anchor, if (rotateVerticalGlyph) { // Vertical-supporting glyphs are laid out in 24x24 point boxes (1 square em) - // In horizontal orientation, the y values for glyphs are below the midline - // and we use a "yOffset" of -17 to pull them up to the middle. + // In horizontal orientation, the y values for glyphs are below the midline. + // If the glyph's baseline is applicable, we take the value of the baseline offset. + // Otherwise, we use a "yOffset" of -17 to pull them up to the middle. // By rotating counter-clockwise around the point at the center of the left // edge of a 24x24 layout box centered below the midline, we align the center // of the glyphs with the horizontal midline, so the yOffset is no longer // necessary, but we also pull the glyph to the left along the x axis. // The y coordinate includes baseline yOffset, thus needs to be accounted // for when glyph is rotated and translated. - const center = new Point(-halfAdvance, halfAdvance - shaping.yOffset); + const yShift = shaping.hasBaseline ? (-glyph.metrics.ascender + glyph.metrics.descender) / 2 : shaping.yOffset; + const center = new Point(-halfAdvance, halfAdvance - yShift); const verticalRotation = -Math.PI / 2; // xHalfWidhtOffsetcorrection is a difference between full-width and half-width // advance, should be 0 for full-width glyphs and will pull up half-width glyphs. const xHalfWidhtOffsetcorrection = ONE_EM / 2 - halfAdvance; - const xOffsetCorrection = new Point(5 - shaping.yOffset - xHalfWidhtOffsetcorrection, 0); + const xOffsetCorrection = new Point(5 - yShift - xHalfWidhtOffsetcorrection, 0); const verticalOffsetCorrection = new Point(...verticalizedLabelOffset); tl._rotateAround(verticalRotation, center)._add(xOffsetCorrection)._add(verticalOffsetCorrection); tr._rotateAround(verticalRotation, center)._add(xOffsetCorrection)._add(verticalOffsetCorrection); diff --git a/src/symbol/shaping.js b/src/symbol/shaping.js index 6764ea1bd9e..fbe854d7a3b 100644 --- a/src/symbol/shaping.js +++ b/src/symbol/shaping.js @@ -44,6 +44,7 @@ export type Shaping = { lineCount: number, text: string, yOffset: number, + hasBaseline: Boolean, }; export type SymbolAnchor = 'center' | 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; @@ -210,7 +211,8 @@ function shapeText(text: Formatted, right: translate[0], writingMode, lineCount: lines.length, - yOffset: -17 // the y offset *should* be part of the font metadata + yOffset: -17, // the y offset *should* be part of the font metadata + hasBaseline: false }; shapeLines(shaping, glyphs, lines, lineHeight, textAnchor, textJustify, writingMode, spacing, allowVerticalPlacement); @@ -499,16 +501,14 @@ function shapeLines(shaping: Shaping, const glyph = positions && positions[codePoint]; if (!glyph) continue; - // The rects have an addditional buffer that is not included in their size. - const glyphPadding = 1.0; - const rectBuffer = 3 + glyphPadding; - // Each glyph's baseline is starting from its acsender, which is the vertical distance - // from the horizontal baseline to the highest ‘character’ coordinate in a font face. - // If ascender is applied, the shaping rect buffer needs to be counted. - // If ascender is not applicable, fall back to use a default baseline yOffset. - // Since we're laying out at 24 points, we need also calculate how much it will move - // when we scale up or down. - const baselineOffset = (hasBaseline ? ((-glyph.metrics.ascender + rectBuffer * 2) * section.scale) : shaping.yOffset) + (lineMaxScale - section.scale) * 24; + // In order to make different fonts aligned, they must share a general baseline that starts from the midline + // of each font face. Baseline offset is the vertical distance from font face's baseline to its top most + // position, which is the half size of the sum (ascender + descender). Since glyph's position is counted + // from the top left corner, the negative shift is needed. So different fonts shares the same baseline but + // with different offset shift. If font's baseline is not applicable, fall back to use a default baseline + // offset, see shaping.yOffset. Since we're laying out at 24 points, we need also calculate how much it will + // move when we scale up or down. + const baselineOffset = (hasBaseline ? ((-glyph.metrics.ascender + glyph.metrics.descender) / 2 * section.scale) : shaping.yOffset) + (lineMaxScale - section.scale) * 24; if (writingMode === WritingMode.horizontal || // Don't verticalize glyphs that have no upright orientation if vertical placement is disabled. @@ -546,6 +546,7 @@ function shapeLines(shaping: Shaping, shaping.bottom = shaping.top + height; shaping.left += -horizontalAlign * maxLineLength; shaping.right = shaping.left + maxLineLength; + shaping.hasBaseline = hasBaseline; } // justify right = 1, left = 0, center = 0.5 diff --git a/test/integration/render-tests/text-font/mixed-fonts-both-with-baseline/expected.png b/test/integration/render-tests/text-font/mixed-fonts-both-with-baseline/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..62b1d6a133fd7e9b3a3b9c1d63a1378e08ae6ee3 GIT binary patch literal 9109 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 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/text-font/mixed-fonts/style.json b/test/integration/render-tests/text-font/mixed-fonts-both-with-baseline/style.json similarity index 97% rename from test/integration/render-tests/text-font/mixed-fonts/style.json rename to test/integration/render-tests/text-font/mixed-fonts-both-with-baseline/style.json index fc86a0c07c5..9178daf1665 100644 --- a/test/integration/render-tests/text-font/mixed-fonts/style.json +++ b/test/integration/render-tests/text-font/mixed-fonts-both-with-baseline/style.json @@ -2,6 +2,7 @@ "version": 8, "metadata": { "test": { + "collisionDebug": true, "height": 300, "width": 300 } diff --git a/test/integration/render-tests/text-font/mixed-fonts/expected.png b/test/integration/render-tests/text-font/mixed-fonts/expected.png deleted file mode 100644 index 74ecbd58683090ff3884cbd068fee4aa4e236780..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9015 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