diff --git a/include/osgText/Glyph b/include/osgText/Glyph index 782b402dd..125cf7476 100644 --- a/include/osgText/Glyph +++ b/include/osgText/Glyph @@ -40,6 +40,7 @@ class GlyphTexture; enum ShaderTechnique { + NO_TEXT_SHADER = 0x0, GREYSCALE = 0x1, SIGNED_DISTANCE_FIELD = 0x2, ALL_FEATURES = GREYSCALE | SIGNED_DISTANCE_FIELD diff --git a/src/osgText/Glyph.cpp b/src/osgText/Glyph.cpp index fbd793cfa..229efd14c 100644 --- a/src/osgText/Glyph.cpp +++ b/src/osgText/Glyph.cpp @@ -173,7 +173,7 @@ void GlyphTexture::copyGlyphImage(Glyph* glyph, Glyph::TextureInfo* info) { _image->dirty(); - if (_shaderTechnique==GREYSCALE) + if (_shaderTechnique<=GREYSCALE) { // OSG_NOTICE<<"GlyphTexture::copyGlyphImage() greyscale copying. glyphTexture="<getTextureWidthHint()); defineList["TEXTURE_DIMENSION"] = osg::StateSet::DefinePair(ss.str(), osg::StateAttribute::ON); + } + if (_shaderTechnique>GREYSCALE) + { defineList["SIGNED_DISTNACE_FIELD"] = osg::StateSet::DefinePair("1", osg::StateAttribute::ON); } @@ -200,7 +206,7 @@ osg::StateSet* Text::createStateSet() #if defined(OSG_GL_FIXED_FUNCTION_AVAILABLE) osg::DisplaySettings::ShaderHint shaderHint = osg::DisplaySettings::instance()->getShaderHint(); - if (_shaderTechnique==GREYSCALE && shaderHint==osg::DisplaySettings::SHADER_NONE) + if (_shaderTechnique==NO_TEXT_SHADER && shaderHint==osg::DisplaySettings::SHADER_NONE) { OSG_NOTICE<<"Font::Font() Fixed function pipeline"<addShader(osgDB::readRefShaderFileWithFallback(osg::Shader::VERTEX, "shaders/text.vert", text_vert)); } - if (_shaderTechnique==GREYSCALE) { - OSG_NOTICE<<"Using shaders/text_greyscale.frag"<addShader(osgDB::readRefShaderFileWithFallback(osg::Shader::FRAGMENT, "shaders/text_greyscale.frag", text_greyscale_frag)); - } - else - { - OSG_NOTICE<<"Using shaders/text_sdf.frag"<addShader(osgDB::readRefShaderFileWithFallback(osg::Shader::FRAGMENT, "shaders/text_sdf.frag", text_sdf_frag)); + #include "shaders/text_frag.cpp" + program->addShader(osgDB::readRefShaderFileWithFallback(osg::Shader::FRAGMENT, "shaders/text.frag", text_frag)); } return stateset.release(); diff --git a/src/osgText/shaders/text_frag.cpp b/src/osgText/shaders/text_frag.cpp new file mode 100644 index 000000000..09b68444b --- /dev/null +++ b/src/osgText/shaders/text_frag.cpp @@ -0,0 +1,257 @@ +char text_frag[] = "$OSG_GLSL_VERSION\n" + "\n" + "#pragma import_defines( BACKDROP_COLOR, SHADOW, OUTLINE, SIGNED_DISTNACE_FIELD, TEXTURE_DIMENSION, GLYPH_DIMENSION)\n" + "\n" + "#ifdef GL_ES\n" + " #extension GL_OES_standard_derivatives : enable\n" + " #ifndef GL_OES_standard_derivatives\n" + " #undef SIGNED_DISTNACE_FIELD\n" + " #endif\n" + "#endif\n" + "\n" + "#if !defined(GL_ES)\n" + " #if __VERSION__>=400\n" + " #define osg_TextureQueryLOD textureQueryLod\n" + " #else\n" + " #extension GL_ARB_texture_query_lod : enable\n" + " #ifdef GL_ARB_texture_query_lod\n" + " #define osg_TextureQueryLOD textureQueryLOD\n" + " #endif\n" + " #endif\n" + "#endif\n" + "\n" + "$OSG_PRECISION_FLOAT\n" + "\n" + "#if __VERSION__>=130\n" + " #define TEXTURE texture\n" + " #define TEXTURELOD textureLod\n" + " out vec4 osg_FragColor;\n" + "#else\n" + " #define TEXTURE texture2D\n" + " #define TEXTURELOD texture2DLod\n" + " #define osg_FragColor gl_FragColor\n" + "#endif\n" + "\n" + "\n" + "#if !defined(GL_ES) && __VERSION__>=130\n" + " #define ALPHA r\n" + " #define SDF g\n" + "#else\n" + " #define ALPHA a\n" + " #define SDF r\n" + "#endif\n" + "\n" + "\n" + "uniform sampler2D glyphTexture;\n" + "\n" + "$OSG_VARYING_IN vec2 texCoord;\n" + "$OSG_VARYING_IN vec4 vertexColor;\n" + "\n" + "#ifndef TEXTURE_DIMENSION\n" + "const float TEXTURE_DIMENSION = 1024.0;\n" + "#endif\n" + "\n" + "#ifndef GLYPH_DIMENSION\n" + "const float GLYPH_DIMENSION = 32.0;\n" + "#endif\n" + "\n" + "#ifdef SIGNED_DISTNACE_FIELD\n" + "\n" + "float distanceFromEdge(vec2 tc)\n" + "{\n" + " float center_alpha = TEXTURELOD(glyphTexture, tc, 0.0).SDF;\n" + " if (center_alpha==0.0) return -1.0;\n" + "\n" + " //float distance_scale = (1.0/4.0)*1.41;\n" + " float distance_scale = (1.0/6.0)*1.41;\n" + " //float distance_scale = (1.0/8.0)*1.41;\n" + "\n" + " return (center_alpha-0.5)*distance_scale;\n" + "}\n" + "\n" + "vec4 distanceFieldColorSample(float edge_distance, float blend_width, float blend_half_width)\n" + "{\n" + "#ifdef OUTLINE\n" + " float outline_width = OUTLINE*0.5;\n" + " if (edge_distance>blend_half_width)\n" + " {\n" + " return vertexColor;\n" + " }\n" + " else if (edge_distance>-blend_half_width)\n" + " {\n" + " return mix(vertexColor, BACKDROP_COLOR, smoothstep(0.0, 1.0, (blend_half_width-edge_distance)/(blend_width)));\n" + " }\n" + " else if (edge_distance>(blend_half_width-outline_width))\n" + " {\n" + " return BACKDROP_COLOR;\n" + " }\n" + " else if (edge_distance>-(outline_width+blend_half_width))\n" + " {\n" + " return vec4(BACKDROP_COLOR.rgb, ((blend_half_width+outline_width+edge_distance)/blend_width));\n" + " }\n" + " else\n" + " {\n" + " return vec4(0.0, 0.0, 0.0, 0.0);\n" + " }\n" + "#else\n" + " if (edge_distance>blend_half_width)\n" + " {\n" + " return vertexColor;\n" + " }\n" + " else if (edge_distance>-blend_half_width)\n" + " {\n" + " return vec4(vertexColor.rgb, smoothstep(1.0, 0.0, (blend_half_width-edge_distance)/(blend_width)));\n" + " }\n" + " else\n" + " {\n" + " return vec4(0.0, 0.0, 0.0, 0.0);\n" + " }\n" + "#endif\n" + "}\n" + "\n" + "vec4 textColor(vec2 src_texCoord)\n" + "{\n" + " float sample_distance_scale = 0.75;\n" + " vec2 dx = dFdx(src_texCoord)*sample_distance_scale;\n" + " vec2 dy = dFdy(src_texCoord)*sample_distance_scale;\n" + "\n" + "\n" + " float distance_across_pixel = length(dx+dy)*(TEXTURE_DIMENSION/GLYPH_DIMENSION);\n" + "\n" + " // compute the appropriate number of samples required to avoid aliasing.\n" + " int maxNumSamplesAcrossSide = 4;\n" + "\n" + " int numSamplesX = int(TEXTURE_DIMENSION * length(dx));\n" + " int numSamplesY = int(TEXTURE_DIMENSION * length(dy));\n" + " if (numSamplesX<2) numSamplesX = 2;\n" + " if (numSamplesY<2) numSamplesY = 2;\n" + " if (numSamplesX>maxNumSamplesAcrossSide) numSamplesX = maxNumSamplesAcrossSide;\n" + " if (numSamplesY>maxNumSamplesAcrossSide) numSamplesY = maxNumSamplesAcrossSide;\n" + "\n" + "\n" + " vec2 delta_tx = dx/float(numSamplesX-1);\n" + " vec2 delta_ty = dy/float(numSamplesY-1);\n" + "\n" + " float numSamples = float(numSamplesX)*float(numSamplesY);\n" + " float scale = 1.0/numSamples;\n" + " vec4 total_color = vec4(0.0,0.0,0.0,0.0);\n" + "\n" + " float blend_width = 1.5*distance_across_pixel/numSamples;\n" + " float blend_half_width = blend_width*0.5;\n" + "\n" + " // check whether fragment is wholly within or outwith glyph body+outline\n" + " float cd = distanceFromEdge(src_texCoord); // central distance (distance from center to edge)\n" + " if (cd-blend_half_width>distance_across_pixel) return vertexColor; // pixel fully within glyph body\n" + "\n" + " #ifdef OUTLINE\n" + " float outline_width = OUTLINE*0.5;\n" + " if ((-cd-outline_width-blend_half_width)>distance_across_pixel) return vec4(0.0, 0.0, 0.0, 0.0); // pixel fully outside outline+glyph body\n" + " #else\n" + " if (-cd-blend_half_width>distance_across_pixel) return vec4(0.0, 0.0, 0.0, 0.0); // pixel fully outside glyph body\n" + " #endif\n" + "\n" + "\n" + " // use multi-sampling to provide high quality antialised fragments\n" + " vec2 origin = src_texCoord - dx*0.5 - dy*0.5;\n" + " for(;numSamplesY>0; --numSamplesY)\n" + " {\n" + " vec2 pos = origin;\n" + " int numX = numSamplesX;\n" + " for(;numX>0; --numX)\n" + " {\n" + " vec4 c = distanceFieldColorSample(distanceFromEdge(pos), blend_width, blend_half_width);\n" + " total_color = total_color + c * c.a;\n" + " pos += delta_tx;\n" + " }\n" + " origin += delta_ty;\n" + " }\n" + "\n" + " total_color.rgb /= total_color.a;\n" + " total_color.a *= scale;\n" + "\n" + " return total_color;\n" + "}\n" + "\n" + "#else\n" + "\n" + "vec4 textColor(vec2 src_texCoord)\n" + "{\n" + "\n" + "#ifdef OUTLINE\n" + "\n" + " float alpha = TEXTURE(glyphTexture, src_texCoord).ALPHA;\n" + " float delta_tc = 1.6*OUTLINE*GLYPH_DIMENSION/TEXTURE_DIMENSION;\n" + "\n" + " float outline_alpha = alpha;\n" + " vec2 origin = src_texCoord-vec2(delta_tc*0.5, delta_tc*0.5);\n" + "\n" + " float numSamples = 3.0;\n" + " delta_tc = delta_tc/(numSamples-1);\n" + "\n" + " float background_alpha = 1.0;\n" + "\n" + " for(float i=0.0; i1.0) outline_alpha = 1.0;\n" + "\n" + " if (outline_alpha==0.0) return vec4(0.0, 0.0, 0.0, 0.0); // outside glyph and outline\n" + "\n" + " vec4 color = mix(BACKDROP_COLOR, vertexColor, smoothstep(0.0, 1.0, alpha));\n" + " color.a = smoothstep(0.0, 1.0, outline_alpha);\n" + "\n" + " return color;\n" + "\n" + "#else\n" + "\n" + " float alpha = TEXTURE(glyphTexture, src_texCoord).ALPHA;\n" + " if (alpha==0.0) vec4(0.0, 0.0, 0.0, 0.0);\n" + " return vec4(vertexColor.rgb, vertexColor.a * alpha);\n" + "\n" + "#endif\n" + "}\n" + "\n" + "#endif\n" + "\n" + "\n" + "void main(void)\n" + "{\n" + " if (texCoord.x<0.0 && texCoord.y<0.0)\n" + " {\n" + " osg_FragColor = vertexColor;\n" + " return;\n" + " }\n" + "\n" + "#ifdef SHADOW\n" + " float scale = -1.0*GLYPH_DIMENSION/TEXTURE_DIMENSION;\n" + " vec2 delta_tc = SHADOW*scale;\n" + " vec4 shadow_color = textColor(texCoord+delta_tc);\n" + " shadow_color.rgb = BACKDROP_COLOR.rgb;\n" + "\n" + " vec4 glyph_color = textColor(texCoord);\n" + " vec4 color = mix(shadow_color, glyph_color, glyph_color.a);\n" + "#else\n" + " vec4 color = textColor(texCoord);\n" + "#endif\n" + "\n" + " if (color.a==0.0) discard;\n" + "\n" + " osg_FragColor = color;\n" + "}\n" + "\n"; diff --git a/src/osgText/shaders/text_greyscale_frag.cpp b/src/osgText/shaders/text_greyscale_frag.cpp deleted file mode 100644 index 6d561fbce..000000000 --- a/src/osgText/shaders/text_greyscale_frag.cpp +++ /dev/null @@ -1,35 +0,0 @@ -char text_greyscale_frag[] = "$OSG_GLSL_VERSION\n" - "$OSG_PRECISION_FLOAT\n" - "\n" - "#pragma import_defines( BACKDROP_COLOR, OUTLINE, ALPHA )\n" - "\n" - "#if __VERSION__>=130\n" - " #define TEXTURE texture\n" - " out vec4 osg_FragColor;\n" - "#else\n" - " #define TEXTURE texture2D\n" - " #define osg_FragColor gl_FragColor\n" - "#endif\n" - "\n" - "uniform sampler2D glyphTexture;\n" - "\n" - "$OSG_VARYING_IN vec2 texCoord;\n" - "$OSG_VARYING_IN vec4 vertexColor;\n" - "\n" - "#ifndef ALPHA\n" - " #if !defined(GL_ES) && __VERSION__>=130\n" - " #define ALPHA r\n" - " #else\n" - " #define ALPHA a\n" - " #endif\n" - "#endif\n" - "\n" - "void main(void)\n" - "{\n" - " float alpha = TEXTURE(glyphTexture, texCoord).ALPHA;\n" - "\n" - " if (alpha==0.0) discard;\n" - "\n" - " osg_FragColor = vec4(vertexColor.rgb, alpha);\n" - "}\n" - "\n"; diff --git a/src/osgText/shaders/text_sdf_frag.cpp b/src/osgText/shaders/text_sdf_frag.cpp deleted file mode 100644 index 1864fc3c3..000000000 --- a/src/osgText/shaders/text_sdf_frag.cpp +++ /dev/null @@ -1,257 +0,0 @@ -char text_sdf_frag[] = "$OSG_GLSL_VERSION\n" - "\n" - "#pragma import_defines( BACKDROP_COLOR, SHADOW, OUTLINE, SIGNED_DISTNACE_FIELD, TEXTURE_DIMENSION, GLYPH_DIMENSION)\n" - "\n" - "#ifdef GL_ES\n" - " #extension GL_OES_standard_derivatives : enable\n" - " #ifndef GL_OES_standard_derivatives\n" - " #undef SIGNED_DISTNACE_FIELD\n" - " #endif\n" - "#endif\n" - "\n" - "#if !defined(GL_ES)\n" - " #if __VERSION__>=400\n" - " #define osg_TextureQueryLOD textureQueryLod\n" - " #else\n" - " #extension GL_ARB_texture_query_lod : enable\n" - " #ifdef GL_ARB_texture_query_lod\n" - " #define osg_TextureQueryLOD textureQueryLOD\n" - " #endif\n" - " #endif\n" - "#endif\n" - "\n" - "$OSG_PRECISION_FLOAT\n" - "\n" - "#if __VERSION__>=130\n" - " #define TEXTURE texture\n" - " #define TEXTURELOD textureLod\n" - " out vec4 osg_FragColor;\n" - "#else\n" - " #define TEXTURE texture2D\n" - " #define TEXTURELOD texture2DLod\n" - " #define osg_FragColor gl_FragColor\n" - "#endif\n" - "\n" - "\n" - "#if !defined(GL_ES) && __VERSION__>=130\n" - " #define ALPHA r\n" - " #define SDF g\n" - "#else\n" - " #define ALPHA a\n" - " #define SDF r\n" - "#endif\n" - "\n" - "\n" - "uniform sampler2D glyphTexture;\n" - "\n" - "$OSG_VARYING_IN vec2 texCoord;\n" - "$OSG_VARYING_IN vec4 vertexColor;\n" - "\n" - "#ifndef TEXTURE_DIMENSION\n" - "const float TEXTURE_DIMENSION = 1024.0;\n" - "#endif\n" - "\n" - "#ifndef GLYPH_DIMENSION\n" - "const float GLYPH_DIMENSION = 32.0;\n" - "#endif\n" - "\n" - "#ifdef SIGNED_DISTNACE_FIELD\n" - "\n" - "float distanceFromEdge(vec2 tc)\n" - "{\n" - " float center_alpha = TEXTURELOD(glyphTexture, tc, 0.0).SDF;\n" - " if (center_alpha==0.0) return -1.0;\n" - "\n" - " //float distance_scale = (1.0/4.0)*1.41;\n" - " float distance_scale = (1.0/6.0)*1.41;\n" - " //float distance_scale = (1.0/8.0)*1.41;\n" - "\n" - " return (center_alpha-0.5)*distance_scale;\n" - "}\n" - "\n" - "vec4 distanceFieldColorSample(float edge_distance, float blend_width, float blend_half_width)\n" - "{\n" - "#ifdef OUTLINE\n" - " float outline_width = OUTLINE*0.5;\n" - " if (edge_distance>blend_half_width)\n" - " {\n" - " return vertexColor;\n" - " }\n" - " else if (edge_distance>-blend_half_width)\n" - " {\n" - " return mix(vertexColor, BACKDROP_COLOR, smoothstep(0.0, 1.0, (blend_half_width-edge_distance)/(blend_width)));\n" - " }\n" - " else if (edge_distance>(blend_half_width-outline_width))\n" - " {\n" - " return BACKDROP_COLOR;\n" - " }\n" - " else if (edge_distance>-(outline_width+blend_half_width))\n" - " {\n" - " return vec4(BACKDROP_COLOR.rgb, ((blend_half_width+outline_width+edge_distance)/blend_width));\n" - " }\n" - " else\n" - " {\n" - " return vec4(0.0, 0.0, 0.0, 0.0);\n" - " }\n" - "#else\n" - " if (edge_distance>blend_half_width)\n" - " {\n" - " return vertexColor;\n" - " }\n" - " else if (edge_distance>-blend_half_width)\n" - " {\n" - " return vec4(vertexColor.rgb, smoothstep(1.0, 0.0, (blend_half_width-edge_distance)/(blend_width)));\n" - " }\n" - " else\n" - " {\n" - " return vec4(0.0, 0.0, 0.0, 0.0);\n" - " }\n" - "#endif\n" - "}\n" - "\n" - "vec4 textColor(vec2 src_texCoord)\n" - "{\n" - " float sample_distance_scale = 0.75;\n" - " vec2 dx = dFdx(src_texCoord)*sample_distance_scale;\n" - " vec2 dy = dFdy(src_texCoord)*sample_distance_scale;\n" - "\n" - "\n" - " float distance_across_pixel = length(dx+dy)*(TEXTURE_DIMENSION/GLYPH_DIMENSION);\n" - "\n" - " // compute the appropriate number of samples required to avoid aliasing.\n" - " int maxNumSamplesAcrossSide = 4;\n" - "\n" - " int numSamplesX = int(TEXTURE_DIMENSION * length(dx));\n" - " int numSamplesY = int(TEXTURE_DIMENSION * length(dy));\n" - " if (numSamplesX<2) numSamplesX = 2;\n" - " if (numSamplesY<2) numSamplesY = 2;\n" - " if (numSamplesX>maxNumSamplesAcrossSide) numSamplesX = maxNumSamplesAcrossSide;\n" - " if (numSamplesY>maxNumSamplesAcrossSide) numSamplesY = maxNumSamplesAcrossSide;\n" - "\n" - "\n" - " vec2 delta_tx = dx/float(numSamplesX-1);\n" - " vec2 delta_ty = dy/float(numSamplesY-1);\n" - "\n" - " float numSamples = float(numSamplesX)*float(numSamplesY);\n" - " float scale = 1.0/numSamples;\n" - " vec4 total_color = vec4(0.0,0.0,0.0,0.0);\n" - "\n" - " float blend_width = 1.5*distance_across_pixel/numSamples;\n" - " float blend_half_width = blend_width*0.5;\n" - "\n" - " // check whether fragment is wholly within or outwith glyph body+outline\n" - " float cd = distanceFromEdge(src_texCoord); // central distance (distance from center to edge)\n" - " if (cd-blend_half_width>distance_across_pixel) return vertexColor; // pixel fully within glyph body\n" - "\n" - " #ifdef OUTLINE\n" - " float outline_width = OUTLINE*0.5;\n" - " if ((-cd-outline_width-blend_half_width)>distance_across_pixel) return vec4(0.0, 0.0, 0.0, 0.0); // pixel fully outside outline+glyph body\n" - " #else\n" - " if (-cd-blend_half_width>distance_across_pixel) return vec4(0.0, 0.0, 0.0, 0.0); // pixel fully outside glyph body\n" - " #endif\n" - "\n" - "\n" - " // use multi-sampling to provide high quality antialised fragments\n" - " vec2 origin = src_texCoord - dx*0.5 - dy*0.5;\n" - " for(;numSamplesY>0; --numSamplesY)\n" - " {\n" - " vec2 pos = origin;\n" - " int numX = numSamplesX;\n" - " for(;numX>0; --numX)\n" - " {\n" - " vec4 c = distanceFieldColorSample(distanceFromEdge(pos), blend_width, blend_half_width);\n" - " total_color = total_color + c * c.a;\n" - " pos += delta_tx;\n" - " }\n" - " origin += delta_ty;\n" - " }\n" - "\n" - " total_color.rgb /= total_color.a;\n" - " total_color.a *= scale;\n" - "\n" - " return total_color;\n" - "}\n" - "\n" - "#else\n" - "\n" - "vec4 textColor(vec2 src_texCoord)\n" - "{\n" - "\n" - "#ifdef OUTLINE\n" - "\n" - " float alpha = TEXTURE(glyphTexture, src_texCoord).ALPHA;\n" - " float delta_tc = 1.6*OUTLINE*GLYPH_DIMENSION/TEXTURE_DIMENSION;\n" - "\n" - " float outline_alpha = alpha;\n" - " vec2 origin = src_texCoord-vec2(delta_tc*0.5, delta_tc*0.5);\n" - "\n" - " float numSamples = 3.0;\n" - " delta_tc = delta_tc/(numSamples-1);\n" - "\n" - " float background_alpha = 1.0;\n" - "\n" - " for(float i=0.0; i1.0) outline_alpha = 1.0;\n" - "\n" - " if (outline_alpha==0.0) return vec4(0.0, 0.0, 0.0, 0.0); // outside glyph and outline\n" - "\n" - " vec4 color = mix(BACKDROP_COLOR, vertexColor, smoothstep(0.0, 1.0, alpha));\n" - " color.a = smoothstep(0.0, 1.0, outline_alpha);\n" - "\n" - " return color;\n" - "\n" - "#else\n" - "\n" - " float alpha = TEXTURE(glyphTexture, src_texCoord).ALPHA;\n" - " if (alpha==0.0) vec4(0.0, 0.0, 0.0, 0.0);\n" - " return vec4(vertexColor.rgb, vertexColor.a * alpha);\n" - "\n" - "#endif\n" - "}\n" - "\n" - "#endif\n" - "\n" - "\n" - "void main(void)\n" - "{\n" - " if (texCoord.x<0.0 && texCoord.y<0.0)\n" - " {\n" - " osg_FragColor = vertexColor;\n" - " return;\n" - " }\n" - "\n" - "#ifdef SHADOW\n" - " float scale = -1.0*GLYPH_DIMENSION/TEXTURE_DIMENSION;\n" - " vec2 delta_tc = SHADOW*scale;\n" - " vec4 shadow_color = textColor(texCoord+delta_tc);\n" - " shadow_color.rgb = BACKDROP_COLOR.rgb;\n" - "\n" - " vec4 glyph_color = textColor(texCoord);\n" - " vec4 color = mix(shadow_color, glyph_color, glyph_color.a);\n" - "#else\n" - " vec4 color = textColor(texCoord);\n" - "#endif\n" - "\n" - " if (color.a==0.0) discard;\n" - "\n" - " osg_FragColor = color;\n" - "}\n" - "\n";