/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * * This source code is licensed under the MIT-style license found in the * LICENSE file in the root directory of this source tree. */ package com.horcrux.svg; import com.facebook.react.bridge.ReadableMap; import java.util.ArrayList; import javax.annotation.Nullable; // https://www.w3.org/TR/SVG/text.html#TSpanElement class GlyphContext { // Current stack (one per node push/pop) final ArrayList mFontContext = new ArrayList<>(); // Unique input attribute lists (only added if node sets a value) private final ArrayList mXsContext = new ArrayList<>(); private final ArrayList mYsContext = new ArrayList<>(); private final ArrayList mDXsContext = new ArrayList<>(); private final ArrayList mDYsContext = new ArrayList<>(); private final ArrayList mRsContext = new ArrayList<>(); // Unique index into attribute list (one per unique list) private final ArrayList mXIndices = new ArrayList<>(); private final ArrayList mYIndices = new ArrayList<>(); private final ArrayList mDXIndices = new ArrayList<>(); private final ArrayList mDYIndices = new ArrayList<>(); private final ArrayList mRIndices = new ArrayList<>(); // Index of unique context used (one per node push/pop) private final ArrayList mXsIndices = new ArrayList<>(); private final ArrayList mYsIndices = new ArrayList<>(); private final ArrayList mDXsIndices = new ArrayList<>(); private final ArrayList mDYsIndices = new ArrayList<>(); private final ArrayList mRsIndices = new ArrayList<>(); // Calculated on push context, percentage and em length depends on parent font size private double mFontSize = FontData.DEFAULT_FONT_SIZE; private FontData topFont = FontData.Defaults; // Current accumulated values // https://www.w3.org/TR/SVG/types.html#DataTypeCoordinate // syntax is the same as that for private double mX; private double mY; // https://www.w3.org/TR/SVG/types.html#Length private double mDX; private double mDY; // Current SVGLengthList // https://www.w3.org/TR/SVG/types.html#InterfaceSVGLengthList // https://www.w3.org/TR/SVG/types.html#DataTypeCoordinates // https://www.w3.org/TR/SVG/text.html#TSpanElementXAttribute private SVGLength[] mXs = new SVGLength[] {}; // https://www.w3.org/TR/SVG/text.html#TSpanElementYAttribute private SVGLength[] mYs = new SVGLength[] {}; // Current SVGLengthList // https://www.w3.org/TR/SVG/types.html#DataTypeLengths // https://www.w3.org/TR/SVG/text.html#TSpanElementDXAttribute private SVGLength[] mDXs = new SVGLength[] {}; // https://www.w3.org/TR/SVG/text.html#TSpanElementDYAttribute private SVGLength[] mDYs = new SVGLength[] {}; // Current SVGLengthList // https://www.w3.org/TR/SVG/types.html#DataTypeNumbers // https://www.w3.org/TR/SVG/text.html#TSpanElementRotateAttribute private double[] mRs = new double[] {0}; // Current attribute list index private int mXsIndex; private int mYsIndex; private int mDXsIndex; private int mDYsIndex; private int mRsIndex; // Current value index in current attribute list private int mXIndex = -1; private int mYIndex = -1; private int mDXIndex = -1; private int mDYIndex = -1; private int mRIndex = -1; // Top index of stack private int mTop; // Constructor parameters private final float mScale; private final float mWidth; private final float mHeight; private void pushIndices() { mXsIndices.add(mXsIndex); mYsIndices.add(mYsIndex); mDXsIndices.add(mDXsIndex); mDYsIndices.add(mDYsIndex); mRsIndices.add(mRsIndex); } GlyphContext(float scale, float width, float height) { mScale = scale; mWidth = width; mHeight = height; mXsContext.add(mXs); mYsContext.add(mYs); mDXsContext.add(mDXs); mDYsContext.add(mDYs); mRsContext.add(mRs); mXIndices.add(mXIndex); mYIndices.add(mYIndex); mDXIndices.add(mDXIndex); mDYIndices.add(mDYIndex); mRIndices.add(mRIndex); mFontContext.add(topFont); pushIndices(); } private void reset() { mXsIndex = mYsIndex = mDXsIndex = mDYsIndex = mRsIndex = 0; mXIndex = mYIndex = mDXIndex = mDYIndex = mRIndex = -1; mX = mY = mDX = mDY = 0; } FontData getFont() { return topFont; } private FontData getTopOrParentFont(GroupView child) { if (mTop > 0) { return topFont; } else { GroupView parentRoot = child.getParentTextRoot(); while (parentRoot != null) { FontData map = parentRoot.getGlyphContext().getFont(); if (map != FontData.Defaults) { return map; } parentRoot = parentRoot.getParentTextRoot(); } return FontData.Defaults; } } private void pushNodeAndFont(GroupView node, @Nullable ReadableMap font) { FontData parent = getTopOrParentFont(node); mTop++; if (font == null) { mFontContext.add(parent); return; } FontData data = new FontData(font, parent, mScale); mFontSize = data.fontSize; mFontContext.add(data); topFont = data; } void pushContext(GroupView node, @Nullable ReadableMap font) { pushNodeAndFont(node, font); pushIndices(); } private SVGLength[] getStringArrayFromReadableArray(ArrayList readableArray) { int size = readableArray.size(); SVGLength[] strings = new SVGLength[size]; for (int i = 0; i < size; i++) { strings[i] = readableArray.get(i); } return strings; } private double[] getDoubleArrayFromReadableArray(ArrayList readableArray) { int size = readableArray.size(); double[] doubles = new double[size]; for (int i = 0; i < size; i++) { SVGLength length = readableArray.get(i); doubles[i] = length.value; } return doubles; } void pushContext( boolean reset, TextView node, @Nullable ReadableMap font, @Nullable ArrayList x, @Nullable ArrayList y, @Nullable ArrayList deltaX, @Nullable ArrayList deltaY, @Nullable ArrayList rotate) { if (reset) { this.reset(); } pushNodeAndFont(node, font); if (x != null && x.size() != 0) { mXsIndex++; mXIndex = -1; mXIndices.add(mXIndex); mXs = getStringArrayFromReadableArray(x); mXsContext.add(mXs); } if (y != null && y.size() != 0) { mYsIndex++; mYIndex = -1; mYIndices.add(mYIndex); mYs = getStringArrayFromReadableArray(y); mYsContext.add(mYs); } if (deltaX != null && deltaX.size() != 0) { mDXsIndex++; mDXIndex = -1; mDXIndices.add(mDXIndex); mDXs = getStringArrayFromReadableArray(deltaX); mDXsContext.add(mDXs); } if (deltaY != null && deltaY.size() != 0) { mDYsIndex++; mDYIndex = -1; mDYIndices.add(mDYIndex); mDYs = getStringArrayFromReadableArray(deltaY); mDYsContext.add(mDYs); } if (rotate != null && rotate.size() != 0) { mRsIndex++; mRIndex = -1; mRIndices.add(mRIndex); mRs = getDoubleArrayFromReadableArray(rotate); mRsContext.add(mRs); } pushIndices(); } void popContext() { mFontContext.remove(mTop); mXsIndices.remove(mTop); mYsIndices.remove(mTop); mDXsIndices.remove(mTop); mDYsIndices.remove(mTop); mRsIndices.remove(mTop); mTop--; int x = mXsIndex; int y = mYsIndex; int dx = mDXsIndex; int dy = mDYsIndex; int r = mRsIndex; topFont = mFontContext.get(mTop); mXsIndex = mXsIndices.get(mTop); mYsIndex = mYsIndices.get(mTop); mDXsIndex = mDXsIndices.get(mTop); mDYsIndex = mDYsIndices.get(mTop); mRsIndex = mRsIndices.get(mTop); if (x != mXsIndex) { mXsContext.remove(x); mXs = mXsContext.get(mXsIndex); mXIndex = mXIndices.get(mXsIndex); } if (y != mYsIndex) { mYsContext.remove(y); mYs = mYsContext.get(mYsIndex); mYIndex = mYIndices.get(mYsIndex); } if (dx != mDXsIndex) { mDXsContext.remove(dx); mDXs = mDXsContext.get(mDXsIndex); mDXIndex = mDXIndices.get(mDXsIndex); } if (dy != mDYsIndex) { mDYsContext.remove(dy); mDYs = mDYsContext.get(mDYsIndex); mDYIndex = mDYIndices.get(mDYsIndex); } if (r != mRsIndex) { mRsContext.remove(r); mRs = mRsContext.get(mRsIndex); mRIndex = mRIndices.get(mRsIndex); } } private static void incrementIndices(ArrayList indices, int topIndex) { for (int index = topIndex; index >= 0; index--) { int xIndex = indices.get(index); indices.set(index, xIndex + 1); } } // https://www.w3.org/TR/SVG11/text.html#FontSizeProperty /** * Get font size from context. * *

‘font-size’ Value: < absolute-size > | < relative-size > | < length > | < percentage > | * inherit Initial: medium Applies to: text content elements Inherited: yes, the computed value is * inherited Percentages: refer to parent element's font size Media: visual Animatable: yes * *

This property refers to the size of the font from baseline to baseline when multiple lines * of text are set solid in a multiline layout environment. * *

For SVG, if a < length > is provided without a unit identifier (e.g., an unqualified number * such as 128), the SVG user agent processes the < length > as a height value in the current user * coordinate system. * *

If a < length > is provided with one of the unit identifiers (e.g., 12pt or 10%), then the * SVG user agent converts the < length > into a corresponding value in the current user * coordinate system by applying the rules described in Units. * *

Except for any additional information provided in this specification, the normative * definition of the property is in CSS2 ([CSS2], section 15.2.4). */ double getFontSize() { return mFontSize; } double nextX(double advance) { incrementIndices(mXIndices, mXsIndex); int nextIndex = mXIndex + 1; if (nextIndex < mXs.length) { mDX = 0; mXIndex = nextIndex; SVGLength string = mXs[nextIndex]; mX = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize); } mX += advance; return mX; } double nextY() { incrementIndices(mYIndices, mYsIndex); int nextIndex = mYIndex + 1; if (nextIndex < mYs.length) { mDY = 0; mYIndex = nextIndex; SVGLength string = mYs[nextIndex]; mY = PropHelper.fromRelative(string, mHeight, 0, mScale, mFontSize); } return mY; } double nextDeltaX() { incrementIndices(mDXIndices, mDXsIndex); int nextIndex = mDXIndex + 1; if (nextIndex < mDXs.length) { mDXIndex = nextIndex; SVGLength string = mDXs[nextIndex]; double val = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize); mDX += val; } return mDX; } double nextDeltaY() { incrementIndices(mDYIndices, mDYsIndex); int nextIndex = mDYIndex + 1; if (nextIndex < mDYs.length) { mDYIndex = nextIndex; SVGLength string = mDYs[nextIndex]; double val = PropHelper.fromRelative(string, mHeight, 0, mScale, mFontSize); mDY += val; } return mDY; } double nextRotation() { incrementIndices(mRIndices, mRsIndex); mRIndex = Math.min(mRIndex + 1, mRs.length - 1); return mRs[mRIndex]; } float getWidth() { return mWidth; } float getHeight() { return mHeight; } }