/// <summary> /// Load the data from the "kernpairs" node /// </summary> /// <param name="xnl">XML node list containing the "kernpairs" node's children</param> private void LoadFontXML_kernpairs(XmlNodeList xnl) { foreach (XmlNode xn in xnl) { if (xn.Name == "kernpair") { var left = GetXMLAttribute(xn, "left")[0]; var right = GetXMLAttribute(xn, "right")[0]; var adjust = GetXMLAttribute(xn, "adjust"); var pair = new KernPair(left, right); Debug.Assert(!m_kernByPair.ContainsKey(pair)); m_kernByPair[pair] = SByte.Parse(adjust); } } }
private static KerningSubTable ReadFormat0Table(int version, TrueTypeDataBytes data, KernCoverage coverage) { var numberOfPairs = data.ReadUnsignedShort(); // ReSharper disable once UnusedVariable var searchRange = data.ReadUnsignedShort(); // ReSharper disable once UnusedVariable var entrySelector = data.ReadUnsignedShort(); // ReSharper disable once UnusedVariable var rangeShift = data.ReadUnsignedShort(); var pairs = new KernPair[numberOfPairs]; for (int i = 0; i < numberOfPairs; i++) { var leftGlyphIndex = data.ReadUnsignedShort(); var rightGlyphIndex = data.ReadUnsignedShort(); var value = data.ReadSignedShort(); pairs[i] = new KernPair(leftGlyphIndex, rightGlyphIndex, value); } return(new KerningSubTable(version, coverage, pairs)); }
private static GameObject GetObject(string s, Material material, float size, float extrudeDepth, int resolution, float characterSpacing, float lineSpacing, bool prime, bool separate) { if (!_initialized) { Debug.LogError("No font information available"); return(null); } if (s == null || s.Length < 1) { Debug.LogError("String can't be null"); return(null); } if (material == null) { material = defaultMaterial; } if (resolution < 1) { resolution = 1; } if (size < 0.001f) { size = 0.001f; } if (extrudeDepth < 0.0f) { extrudeDepth = 0.0f; } if (characterSpacing < 0.0f) { characterSpacing = 0.0f; } defaultFont = Mathf.Clamp(defaultFont, 0, _fontInfo.Length - 1); List <CommandData> commandData; int commandIndex = 0; char[] chars = ParseString(s, out commandData); int charCount = chars.Length; var glyphIndices = new int[charCount]; int totalVertCount = 0; int totalTriCount = 0; bool extrude = (extrudeDepth > 0.0f); float spacePercent = (characterSpacing < 1.0f)? characterSpacing : 1.0f; float spaceAdd = (characterSpacing > 1.0f)? characterSpacing - 1.0f : 0.0f; var thisFont = _fontInfo[defaultFont]; int fontNumber = defaultFont; // Get total vertex and triangle count, initializing glyph data as necessary for (int i = 0; i < charCount; i++) { var thisCommand = commandData[commandIndex]; while (thisCommand.index == i) { if (thisCommand.command == Command.Font) { int thisFontNumber = (int)thisCommand.data; if (thisFontNumber >= 0 && thisFontNumber < _fontInfo.Length) { fontNumber = thisFontNumber; thisFont = _fontInfo[fontNumber]; } } thisCommand = commandData[++commandIndex]; } if (thisFont == null) { Debug.LogError("Font is null"); return(null); } // Set up glyphs if they haven't been previously initialized var character = chars[i]; if (!thisFont.glyphDictionary.ContainsKey(character)) { if (!thisFont.SetGlyphData(character)) { return(null); } } var glyphData = thisFont.glyphDictionary[character]; glyphIndices[i] = glyphData.glyphIndex; if (glyphData.isVisible) { if (glyphData.resolution != resolution) { if (!glyphData.SetMeshData(resolution)) { Debug.LogWarning("Triangulation failed for char code " + Convert.ToInt32(character) + " (" + character + ")"); continue; } if (!extrude) { glyphData.SetFrontData(); } else { if (includeBackface) { glyphData.SetData(); } else { glyphData.SetFrontAndEdgeData(); } } } if (!separate) { totalVertCount += glyphData.vertexCount; totalTriCount += glyphData.triCount; } } } if (totalVertCount > 65534) { Debug.LogError("Too many points...use fewer characters or reduce resolution"); return(null); } if (!separate && totalVertCount == 0) { Debug.LogError("No usable characters in string"); return(null); } if (prime) { return(null); } GameObject goParent = separate? new GameObject() : null; // Use vertex colors if anything other than Color.white is specified anywhere Color thisColor = defaultColor; var useColors = false; if (thisColor == Color.white) { for (var i = 0; i < commandData.Count; i++) { if (commandData[i].command == Command.Color && (Color)commandData[i].data != Color.white) { useColors = true; break; } } } else { useColors = true; } var totalVerts = new Vector3[totalVertCount]; var totalTris = new int[totalTriCount]; var meshUVs = new Vector2[totalVertCount]; var meshColors = new Color[totalVertCount]; int vertIndex = 0; int triIndex = 0; float baseScale = 1.0f / thisFont.unitsPerEm; bool uvsPerLetter = separate? true : texturePerLetter; Vector3[] thisVerts; int[] thisTris; var kernPair = new KernPair(); float smallestX = float.MaxValue; float largestX = -float.MaxValue; float smallestY = float.MaxValue; float largestY = -float.MaxValue; List <float> lineLengths = null; List <Justify> lineJustifies = null; int loopType = BUILDMESH; bool hasMultipleLines = false; int lineCount = 0; float longestLength = 0.0f; var thisJustify = defaultJustification; if (Array.IndexOf(chars, '\n') != -1) { lineLengths = new List <float>(); lineJustifies = new List <Justify>(); loopType = PREPROCESS; hasMultipleLines = true; } while (loopType > 0) { float horizontalPosition = 0.0f; float verticalPosition = 0.0f; float zPosition = 0.0f; float thisSize = size; thisFont = _fontInfo[defaultFont]; commandIndex = 0; if (hasMultipleLines && loopType == BUILDMESH) { thisJustify = lineJustifies[0]; } var thisCommand = commandData[0]; for (int i = 0; i < charCount; i++) { while (thisCommand.index == i) { switch (thisCommand.command) { case Command.Size: thisSize = (float)thisCommand.data; if (thisSize < .001f) { thisSize = .001f; } break; case Command.Color: thisColor = (Color)thisCommand.data; break; case Command.Font: fontNumber = (int)thisCommand.data; if (fontNumber >= 0 && fontNumber < _fontInfo.Length) { thisFont = _fontInfo[fontNumber]; baseScale = 1.0f / thisFont.unitsPerEm; } break; case Command.Zpos: zPosition = (float)thisCommand.data; break; case Command.Depth: extrudeDepth = (float)thisCommand.data; if (extrudeDepth < 0.0f) { extrudeDepth = 0.0f; } break; case Command.Space: horizontalPosition += (float)thisCommand.data * thisSize; break; case Command.Justify: if (loopType == PREPROCESS) { thisJustify = ((Justify)thisCommand.data); } break; } thisCommand = commandData[++commandIndex]; } float scaleFactor = baseScale * thisSize; var character = chars[i]; if (character == '\0') { continue; } if (character == '\n') { if (loopType == PREPROCESS) { lineLengths.Add(horizontalPosition); lineJustifies.Add(thisJustify); } else if (hasMultipleLines) { if (++lineCount < lineJustifies.Count) { thisJustify = lineJustifies[lineCount]; } } verticalPosition -= thisFont.lineHeight * lineSpacing * scaleFactor; horizontalPosition = 0.0f; continue; } int thisGlyphIdx = glyphIndices[i]; // Kerning if (thisFont.hasKerning && i > 0) { kernPair.left = glyphIndices[i - 1]; kernPair.right = thisGlyphIdx; if (thisFont.kernDictionary.ContainsKey(kernPair)) { horizontalPosition += thisFont.kernDictionary[kernPair] * scaleFactor; } } var glyphData = thisFont.glyphDictionary[character]; // Copy tris/verts to combined mesh int vertexCount = glyphData.vertexCount; if (vertexCount > 0 && loopType == BUILDMESH) { if (glyphData.scaleFactor != scaleFactor) { glyphData.ScaleVertices(scaleFactor, extrude, includeBackface); } if (extrude && glyphData.extrudeDepth != extrudeDepth) { glyphData.SetExtrudeDepth(extrudeDepth, includeBackface); } thisVerts = glyphData.vertices; if (separate) { totalVerts = new Vector3[vertexCount]; totalTris = new int[glyphData.triCount]; meshUVs = new Vector2[vertexCount]; if (useColors) { meshColors = new Color[vertexCount]; } vertIndex = 0; triIndex = 0; } // Get min/max bounds (for UVs if not per-letter, plus anchor position) float max = glyphData.xMax * scaleFactor + horizontalPosition; float min = glyphData.xMin * scaleFactor + horizontalPosition; if (max > largestX) { largestX = max; } if (min < smallestX) { smallestX = min; } max = glyphData.yMax * scaleFactor + verticalPosition; min = glyphData.yMin * scaleFactor + verticalPosition; if (max > largestY) { largestY = max; } if (min < smallestY) { smallestY = min; } if (uvsPerLetter) { float xMax = glyphData.xMax * scaleFactor; float xMin = glyphData.xMin * scaleFactor; float yMax = glyphData.yMax * scaleFactor; float yMin = glyphData.yMin * scaleFactor; float xRange = xMax - xMin; float yRange = yMax - yMin; for (int j = 0; j < vertexCount; j++) { meshUVs[j + vertIndex].x = (thisVerts[j].x - xMin) / xRange; meshUVs[j + vertIndex].y = (thisVerts[j].y - yMin) / yRange; } } if (useColors) { for (int j = 0; j < vertexCount; j++) { meshColors[j + vertIndex] = thisColor; } } thisTris = glyphData.triangles; int triCount = glyphData.triCount; for (int j = 0; j < triCount; j += 3) { totalTris[triIndex] = thisTris[j] + vertIndex; totalTris[triIndex + 1] = thisTris[j + 1] + vertIndex; totalTris[triIndex + 2] = thisTris[j + 2] + vertIndex; triIndex += 3; } // Set vertices with appropriate line justification if (hasMultipleLines && thisJustify != Justify.Left && longestLength != lineLengths[lineCount]) { float addSpace = (thisJustify == Justify.Right)? longestLength - lineLengths[lineCount] : (longestLength - lineLengths[lineCount]) / 2; if (!separate) { for (int j = 0; j < vertexCount; j++) { totalVerts[vertIndex].x = thisVerts[j].x + horizontalPosition + addSpace; totalVerts[vertIndex].y = thisVerts[j].y + verticalPosition; totalVerts[vertIndex++].z = thisVerts[j].z + zPosition; } } else { for (int j = 0; j < vertexCount; j++) { totalVerts[vertIndex].x = thisVerts[j].x + addSpace; totalVerts[vertIndex].y = thisVerts[j].y; totalVerts[vertIndex++].z = thisVerts[j].z + zPosition; } } } else { if (!separate) { for (int j = 0; j < vertexCount; j++) { totalVerts[vertIndex].x = thisVerts[j].x + horizontalPosition; totalVerts[vertIndex].y = thisVerts[j].y + verticalPosition; totalVerts[vertIndex++].z = thisVerts[j].z + zPosition; } } else { for (int j = 0; j < vertexCount; j++) { totalVerts[vertIndex].x = thisVerts[j].x; totalVerts[vertIndex].y = thisVerts[j].y; totalVerts[vertIndex++].z = thisVerts[j].z + zPosition; } } } // Create mesh and game object for individual letters if (separate) { var charMesh = new Mesh(); charMesh.name = character.ToString(); charMesh.vertices = totalVerts; charMesh.uv = meshUVs; if (useColors) { charMesh.colors = meshColors; } charMesh.triangles = totalTris; charMesh.RecalculateNormals(); var charGo = new GameObject(character.ToString(), typeof(MeshFilter), typeof(MeshRenderer)); charGo.GetComponent <MeshFilter>().mesh = charMesh; if (colliderType == ColliderType.Mesh || colliderType == ColliderType.ConvexMesh) { var meshCollider = charGo.AddComponent <MeshCollider>(); meshCollider.sharedMesh = charMesh; meshCollider.convex = (colliderType == ColliderType.ConvexMesh); meshCollider.sharedMaterial = physicsMaterial; } else if (colliderType == ColliderType.Box) { var boxCollider = charGo.AddComponent <BoxCollider>(); boxCollider.sharedMaterial = physicsMaterial; } if (addRigidbodies) { charGo.AddComponent(typeof(Rigidbody)); } charGo.renderer.sharedMaterial = material; charGo.transform.parent = goParent.transform; charGo.transform.position = new Vector3(horizontalPosition, verticalPosition, zPosition); } } horizontalPosition += (thisFont.advanceArray[thisGlyphIdx] + spaceAdd / baseScale) * (scaleFactor * spacePercent); } if (loopType-- == PREPROCESS) { lineLengths.Add(horizontalPosition); lineJustifies.Add(thisJustify); longestLength = lineLengths[0]; for (int i = 1; i < lineLengths.Count; i++) { if (lineLengths[i] > longestLength) { longestLength = lineLengths[i]; } } } } // Set UVs for complete mesh, if not per-letter if (!uvsPerLetter) { float xRange = largestX - smallestX; float yRange = largestY - smallestY; for (int i = 0; i < totalVertCount; i++) { meshUVs[i].x = (totalVerts[i].x - smallestX) / xRange; meshUVs[i].y = (totalVerts[i].y - smallestY) / yRange; } } var add = Vector3.zero; switch (anchor) { case TextAnchor.UpperLeft: add.y = largestY; break; case TextAnchor.UpperCenter: add.x = (largestX - smallestX) * .5f; add.y = largestY; break; case TextAnchor.UpperRight: add.x = largestX - smallestX; add.y = largestY; break; case TextAnchor.MiddleLeft: add.y = (smallestY - largestY) * .5f + largestY; break; case TextAnchor.MiddleCenter: add.x = (largestX - smallestX) * .5f; add.y = (smallestY - largestY) * .5f + largestY; break; case TextAnchor.MiddleRight: add.x = largestX - smallestX; add.y = (smallestY - largestY) * .5f + largestY; break; case TextAnchor.LowerLeft: add.y = (smallestY - largestY) + largestY; break; case TextAnchor.LowerCenter: add.x = (largestX - smallestX) * .5f; add.y = (smallestY - largestY) + largestY; break; case TextAnchor.LowerRight: add.x = largestX - smallestX; add.y = (smallestY - largestY) + largestY; break; } if (extrude) { switch (zAnchor) { case ZAnchor.Middle: add.z = defaultDepth * .5f; break; case ZAnchor.Back: add.z = defaultDepth; break; } } if (!separate) { for (int i = 0; i < totalVertCount; i++) { totalVerts[i] -= add; } } else { var gos = goParent.GetComponentsInChildren <Transform>(); for (int i = 0; i < gos.Length; i++) { if (gos[i].gameObject == goParent.gameObject) { continue; } gos[i].position -= add; } } // Get gameobject name from string, and create mesh and game object if not separate var charString = new string(chars); var name = charString.Substring(0, Mathf.Min(20, charString.Length)); name = name.Replace("\n", " "); name = name.Replace("\0", ""); if (separate) { goParent.name = "3DText " + name; return(goParent); } var mesh = new Mesh(); mesh.name = name; mesh.vertices = totalVerts; mesh.uv = meshUVs; if (useColors) { mesh.colors = meshColors; } mesh.triangles = totalTris; mesh.RecalculateNormals(); var textGo = new GameObject("3DText " + name, typeof(MeshFilter), typeof(MeshRenderer)); textGo.GetComponent <MeshFilter>().mesh = mesh; textGo.renderer.sharedMaterial = material; if (colliderType == ColliderType.Box) { var boxCollider = textGo.AddComponent <BoxCollider>(); boxCollider.sharedMaterial = physicsMaterial; } if (addRigidbodies) { textGo.AddComponent <Rigidbody>(); } return(textGo); }